package org.example.mall.admin.configuration.file;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.example.mall.admin.configuration.exception.BaseException;
import org.example.mall.admin.model.entity.manage.SysFile;
import org.example.mall.admin.service.manage.SysFileService;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.http.HttpHeaders;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.DigestUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

/**
 * @author hzq
 * @date 2022/1/6 21:21
 */
@Slf4j
public class SysFileUtil {

    /**
     * 从地址中提取文件名称
     *
     * @param urls 单个或多个地址以","分割
     *             "/manage/sysFile?bucket=avatar&fileName=2022/1/7/9c37c85873cc4d00a930b078852487d0.jpg,
     *             /manage/sysFile?bucket=avatar&fileName=2022/1/7/9c37c85873cc4d00a930b078852487d2.jpg"
     * @return 文件名列表 ["2022/1/7/9c37c85873cc4d00a930b078852487d2.jpg","2022/1/7/9c37c85873cc4d00a930b078852487d0.jpg"]
     */
    public static Set<String> getFileNames(String urls) {
        if (StrUtil.isBlank(urls))
            return new HashSet<>();
        return getFileNames(Arrays.asList(urls.split(StrPool.COMMA)));
    }

    /**
     * 提取文件名称
     *
     * @param urls 地址列表
     *             ["/manage/sysFile?bucket=avatar&fileName=2022/1/7/9c37c85873cc4d00a930b078852487d0.jpg",
     *             "/manage/sysFile?bucket=avatar&fileName=2022/1/7/9c37c85873cc4d00a930b078852487d2.jpg"]
     * @return 文件名列表 ["2022/1/7/9c37c85873cc4d00a930b078852487d2.jpg","2022/1/7/9c37c85873cc4d00a930b078852487d0.jpg"]
     */
    public static Set<String> getFileNames(Collection<String> urls) {
        Set<String> result = new HashSet<>();
        if (CollUtil.isEmpty(urls))
            return result;
        for (String url : urls) {
            result.add(HttpUtil.decodeParamMap(url, CharsetUtil.CHARSET_UTF_8).get("fileName"));
        }
        return result;
    }

    /**
     * 从对象中查找带有地址注释的属性
     *
     * @param obj 对象
     * @return 地址属性列表 [Map<Url,Field>,Map<Url,Field>]
     */
    public static List<Map<Url, Field>> findUrlFiled(Object obj) {
        List<Map<Url, Field>> result = new ArrayList<>();
        Class<?> clazz = obj.getClass();
        Field[] fields;
        do {
            fields = clazz.getDeclaredFields();
            if (fields.length > 0) {
                for (Field field : fields) {
                    if (field.isAnnotationPresent(Url.class)) {
                        Map<Url, Field> filedMap = new HashMap<>();
                        filedMap.put(field.getAnnotation(Url.class), field);
                        result.add(filedMap);
                    }
                }
            }
            clazz = clazz.getSuperclass();
        } while (clazz != Object.class && clazz != null);
        return result;
    }

    /**
     * 查找对象地址属性的文件列表
     *
     * @param obj 对象
     * @return 文件列表 ["2022/1/7/9c37c85873cc4d00a930b078852487d2.jpg","2022/1/7/9c37c85873cc4d00a930b078852487d0.jpg"]
     */
    public static Set<String> getFileNames(Object obj) {
        Set<String> result = new HashSet<>();
        for (Map<Url, Field> filedMap : findUrlFiled(obj)) {
            for (Url url : filedMap.keySet()) {
                Field field = filedMap.get(url);
                try {
                    field.setAccessible(true);
                    Object value = field.get(obj);
                    if (value == null) continue;
                    switch (url.value()) {
                        case STRING:
                            result.addAll(getFileNames((String) value));
                            break;
                        case COLLECTION:
                            result.addAll(getFileNames((Collection<String>) value));
                            break;
                        case TEXT:
                            result.addAll(getFileNames(getTextUrls((String) value)));
                            break;
                    }
                } catch (ClassCastException | IllegalAccessException ignored) {
                    log.error("附件属性类型标记错误:{}.{}", obj.getClass().getName(), field.getName());
                    log.error("附件属性类型标记错误消息:{}", ignored.getLocalizedMessage());
                }
            }
        }
        result.remove(null);
        return result;
    }

    /**
     * 获取富文本中视频、音频、图片的地址
     *
     * @param text 富文本
     * @return 地址列表 ["http://domain/1.png","https://domain/2.png","/domain/3.png"]
     */
    public static Set<String> getTextUrls(String text) {
        Set<String> result = new HashSet<>();
        if (StrUtil.isBlank(text))
            return result;
        Document document = Jsoup.parse(text);
        Elements elements = document.select("img[src]");
        // Elements elements = document.select("img[src],video[src],audio[src]"); //支持逗号分割
        // elements.addAll(doc.select("video[src]"));
        // elements.addAll(doc.select("audio[src]"));
        for (Element element : elements) {
            String url = element.attr("src");
            if (StrUtil.isNotBlank(url)) {
                result.add(url);
            }
        }
        return result;
    }

    /**
     * @param text 富文本
     * @return 网络附件列表 ["http://domain/1.png","https://domain/2.png"]
     */
    public static Set<String> getNetUrls(String text) {
        return getNetUrls(getTextUrls(text));
    }

    /**
     * @param textUrls ["http://domain/1.png","https://domain/2.png","/domain/3.png"]
     * @return 网络附件列表 ["http://domain/1.png","https://domain/2.png"]
     */
    public static Set<String> getNetUrls(Set<String> textUrls) {
        return textUrls.stream().filter(url -> {
            boolean http = StrUtil.startWith(url, "http://");
            boolean https = StrUtil.startWith(url, "https://");
            return http || https;
        }).collect(Collectors.toSet());
    }

    /**
     * 替换网络图片
     *
     * @param text        富文本
     * @param fileService 系统文件管理
     * @return 替换网络地址为本系统地址后的新富文本
     */
    public static BaseCallback replaceNetBaseCallback(String text, SysFileService fileService) {
        BaseCallback baseCallback = new BaseCallback();
        baseCallback.setContent(replaceNetUrlToSysUrl(text, netUrlToSysUrl(getNetUrls(text), fileService)));
        return baseCallback;
    }

    /**
     * @param text           富文本
     * @param netUrlToSysUrl Map<netUrl,sysURL> netUrl 网络地址 sysURL 本系统地址
     * @return 替换网络地址为本系统地址后的新富文本
     */
    public static String replaceNetUrlToSysUrl(String text, Map<String, String> netUrlToSysUrl) {
        if (MapUtil.isNotEmpty(netUrlToSysUrl)) {
            for (Map.Entry<String, String> next : netUrlToSysUrl.entrySet()) {
                text = text.replace(next.getKey(), next.getValue());
            }
        }
        return text;
    }

    /**
     * 下载网络文件到本系统中
     *
     * @param netUrls     网络地址列表
     * @param fileService 本系统文件服务
     * @return Map<netUrl, sysUrl> 网络地址对应本系统文件访问地址
     */
    public static Map<String, String> netUrlToSysUrl(Set<String> netUrls, SysFileService fileService) {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = fileService.applicationTaskExecutor();
        Map<String, Future<String>> taskMap = new HashMap<>();
        for (String netUrl : netUrls) {
            taskMap.put(netUrl, threadPoolTaskExecutor.submit(netFileToMinio(netUrl, fileService, BucketEnum.MATERIAL.getBucketName(), null)));
        }
        Map<String, String> netUrlToSysUrl = new HashMap<>();
        for (Map.Entry<String, Future<String>> entry : taskMap.entrySet()) {
            try {
                String netUrl = entry.getKey();
                String sysUrl = entry.getValue().get();
                if (StrUtil.isNotEmpty(sysUrl)) {
                    netUrlToSysUrl.put(netUrl, sysUrl);
                }
            } catch (ExecutionException | InterruptedException ignored) {
            }
        }
        return netUrlToSysUrl;
    }

    /**
     * @param text        富文本
     * @param fileService 本系统文件服务
     * @return [0] 替换网络地址为本系统地址后的新富文本 [1] 富文本地址列表 "2022/1/7/9c37c85873cc4d00a930b078852487d2.jpg,2022/1/7/9c37c85873cc4d00a930b078852487d0.jpg"
     */
    public static UrlsCallback replaceNetUrlsCallback(String text, SysFileService fileService) {
        Set<String> textUrls = getTextUrls(text);
        Map<String, String> netUrlToSysUrl = netUrlToSysUrl(getNetUrls(textUrls), fileService);
        for (Map.Entry<String, String> entry : netUrlToSysUrl.entrySet()) {
            textUrls.remove(entry.getKey());
            textUrls.add(entry.getValue());
        }
        UrlsCallback urlsCallback = new UrlsCallback();
        urlsCallback.setContent(replaceNetUrlToSysUrl(text, netUrlToSysUrl));
        urlsCallback.setUrls(textUrls.stream().collect(Collectors.joining(StrPool.COMMA)));
        return urlsCallback;
    }

    /**
     * 下载网络文件上传minio服务器 只允许上传视频或图片
     *
     * @param netUrl       网络地址
     * @param fileService  文件服务
     * @param bucket       上传到 minio 的哪个桶
     * @param originalName 文件名称
     * @return 本系统中的文件地址 /manage/sysFile?bucket=avatar&fileName=2022/1/7/9c37c85873cc4d00a930b078852487d0.jpg
     */
    public static Callable<String> netFileToMinio(String netUrl, SysFileService fileService, String bucket, String originalName) {
        return () -> {
            HttpResponse response = HttpUtil.createGet(netUrl).timeout(-1).executeAsync();
            String originalFileName = StrUtil.isBlank(originalName) ? "网络附件" : originalName;
            String contentType = response.header(HttpHeaders.CONTENT_TYPE);
            if (StrUtil.isNotBlank(contentType)) {
                String[] type = contentType.split(StrPool.SLASH);
                if (!Arrays.asList("image", "video").contains(type[0].toLowerCase())) {
                    throw new BaseException("只支持下载网络图片或视频");
                }
                originalFileName += (StrPool.DOT + type[1]);
            }
            try {
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                long fileSize = response.writeBody(outputStream, false, null);
                byte[] bytes = outputStream.toByteArray();
                // 判断系统中是否已存在该文件
                String md5Hex = DigestUtils.md5DigestAsHex(bytes);
                SysFile sysFile = fileService.getSysFile(md5Hex);
                String bucketName = fileService.extractBucketName(bucket);
                if (sysFile != null) {
                    if (!bucketName.equals(sysFile.getClassification())) {
                        sysFile.setClassification(bucketName);
                        fileService.updateById(sysFile);
                    }
                    return sysFile.getUrl();
                }
                String fileName = fileService.extractFileName(originalFileName, md5Hex);
                fileService.minioTemplate().putObject(bucketName, fileName, new ByteArrayInputStream(bytes), fileSize, contentType);
                sysFile = fileService.saveSysFileLog(bucketName, originalFileName, fileName, fileSize, md5Hex, netUrl, contentType, null);
                return sysFile.getUrl();
            } catch (Exception e) {
                throw new BaseException(e.getLocalizedMessage());
            }
        };
    }

}
